# 本文件定义familytalk server端实现，属于应用层
import base
import message
import serversql
import serverso
import datetime
import time


class Person():

    def __init__(self, id = None, name = None, islogin=None,lastlogintime = None):
        self.__id = id
        self.__name = name
        self.__islogin = islogin
        self.__lastlogintime =lastlogintime

    def get_lastlogintime(self):
        return self.__lastlogintime

    def set_lastlogintime(self,lastlogintime):
        self.__lastlogintime = lastlogintime

    def islogin(self):
        return self.__islogin

    def login(self):
        self.__islogin = True

    def logout(self):
        self.__islogin = False

    def getid(self):
        return self.__id

    def getname(self):
        return self.__name

class Family():
    def __init__(self, id=None, name=None, personids=None):
        self.__id = id
        self.__name = name
        self.__personids = personids

    def setid(self, id):
        self.__id = id

    def getid(self):
        return self.__id

    def setname(self, name):
        self.__name = name

    def getname(self):
        return self.__name

    def setpersonids(self, personids):
        self.__personids = personids

    def getpersonids(self):
        return self.__personids

    def appendpersonid(self,personid):
        if self.__personids is None:
            self.__personids = []
        self.__personids.append(personid)


class serverdo(base.base):
    def __init__(self,s_address):
        super(serverdo, self).__init__()
        # 记录person的字典，根据id查找person对象{id:person}
        self.__persondict = {0: Person()}  # 为了让pycharm能够联想，这里加一个默认表达格式,正式运行时，需要删掉
        self.__familydict = {0: Family()}  # 为了让pycharm能够联想，这里加一个默认表达格式,正式运行时，需要删掉

        self.__mysql = serversql.talksql()
        self.__mysql.open()
        self.__ftmsg = message.Ft_message()
        self.__serverso = serverso.serverso(s_address, self.serverdo_in_who, self.serverdo_in)

        self.__persondict_init()
        self.__familydict_init()
        self._setlogName('ServerDo')

    # 定义一些接口用于显示动态数据
    def showsosodict(self):  # 显示连接表
        self.__serverso.showsodict()
    def showsoiddict(self):  # 显示id conn交换表member
        self.__serverso.showiddict()

    def showpersondict(self):  # 显示成员表
        print('here are person diction')
        #print(self.__persondict)
        for e in self.__persondict:
            #print("{%d:%s}"%(e,str(self.__persondict.get(e))))
            person = self.__persondict.get(e)
            print("{%d:name=%s,islogin=%s,lastlogintime=%s}"%
                  (e,person.getname(),str(person.islogin()),str(person.get_lastlogintime())))
        print("total is :%d" % self.__persondict.__len__())

    def showfamilydict(self):  # 显示成员表
        print('here are family diction')
        #print(self.__familydict)
        for e in self.__familydict:
            family = self.__familydict.get(e)
            personids = family.getpersonids()
            person_num = 0
            if personids is not None:
                person_num = personids.__len__()
            print("{%d:name=%s,person_num=%d}" %(e, family.getname(),person_num))
        print("total is :%d" % self.__familydict.__len__())

    def server_run(self):
        self.__serverso.server_run()

    def tuple_to_list(self,tup,list):
        for e in tup:
            list.append(e)
        return list

    def __persondict_init(self):#将所有login的person导入
        persons = self.__mysql.getallperson_login()
        for person in persons:
            id,name,pw,islogin,lastlogintime = person
            islogin_bool = False
            if islogin==1:
                islogin_bool = True
            newperson = Person(id,name,islogin_bool,time.mktime(lastlogintime.timetuple()))# 内存中time格式为时间戳
            self.__persondict.update({id: newperson})
            print("__persondict_init lastlogintime:",newperson.get_lastlogintime())

    def __familydict_init(self):
        #目前将数据库中所有的family导入，将来支持老化
        familys = self.__mysql.getallfamily()
        for family in familys:
            familyid,familyname = family
            newfamily = Family(familyid,familyname)
            self.__familydict.update({familyid:newfamily})
            persons= self.__mysql.get_familyperson(familyid)
            for person in persons:
                personid,=person
                newfamily.appendpersonid(personid)

    def __make_person_rfdict(self,personid):
        familyids = self.__mysql.get_personfamily(personid)
        familylist = []
        for familyid in familyids:#familyid是单个元组
            familypersonids = self.__mysql.get_familyperson(familyid)
            familypersonlist = []
            for familypersonid in familypersonids:
                familyperson = self.__mysql.getperson(familypersonid)
                familypersondict = {'id': familypersonid[0], 'name': familyperson[0]}
                familypersonlist.append(familypersondict)
            family = self.__mysql.getfamily(familyid)
            familydict = {'id': familyid[0], 'name': family[0], 'persons': familypersonlist}
            familylist.append(familydict)
        return {'familys': familylist}  # 目前没有friends

    def __make_person_leavesayslist(self,personid):
        leavesayss = self.__mysql.get_leavesays(personid)
        leavesaysslist = []
        for leavesays in leavesayss:
            leavesayslist = []
            self.tuple_to_list(leavesays, leavesayslist)
            #时间格式要改一下，暂时填0
            saystime_datetime = leavesayslist[3]
            #int(round(time.time() * 1000))
            #sql为datetime，需要转换为时间戳，python时间戳为浮点，需要*1000 （int(round(time.time() * 1000))）转换为整形java格式
            leavesayslist[3] = int(round(time.mktime(saystime_datetime.timetuple())*1000))
            #数据库修改为binary,这里取出的says格式要转换一下
            says_bytes = leavesayslist[4]
            says = str(says_bytes,encoding="utf-8")
            leavesayslist[4] = says
            leavesaysslist.append(leavesayslist)
        return leavesaysslist

    def __do_auth_req(self,so, id, password):
        try:
            name, pw,islogin,lastlogintime = self.__mysql.getperson(id)
        except:
            send_bytes = self.__ftmsg.pack_auth_rej(-1)
            self._log("anth rej:id =%d,reason is 'id is not in mysql'." % id)
            return -1, send_bytes
            # 以下处理认证

        if password == pw:
            ftmsg_bytes = self.__ftmsg.pack_auth_ack(name)
            self._log("auth ack:id =%d" % id)
            if self.__persondict.get(id) is None:
                # person = Person(id, name, True)
                # 认证通过即登录
                lastlogintime = datetime.datetime.now()
                self.__mysql.updateperson_login(id,lastlogintime)
                print("lastlogintime:",lastlogintime)
                person = self.__persondict.get(id)
                if person is not None:
                    person.login()
                    person.set_lastlogintime(time.mktime(lastlogintime.timetuple()))#数据库中保存datetime，内存中为时间戳
                else:
                    person = Person(id,name,True,lastlogintime)
                    self.__persondict.update({id: person})
                self._log('update {%d:%s} to persondict'%(id,str(self.__persondict.get(id))),self.LOG_DEBUG)

            #这条路径存在重复登陆，可对应多路径多设备登录
            self.__serverso.soiswho(so,id)
            self.__serverso.sendftmsg(id,ftmsg_bytes)

            #推送亲友字典
            rfdict = self.__make_person_rfdict(id)
            self._log("make %d's rfdict:%s" % (id, str(rfdict)), self.LOG_DEBUG)
            ftmsg_bytes = self.__ftmsg.pack_rf_update(rfdict)
            self._log("rf update", self.LOG_DEBUG)
            self.__serverso.sendftmsg(id,ftmsg_bytes)
            #推送留言
            leavesayslist = self.__make_person_leavesayslist(id)
            self._log("make %d's leavesayslist:%s" % (id, str(leavesayslist)), self.LOG_DEBUG)

            ftmsg_bytes = self.__ftmsg.pack_leavesays(leavesayslist)
            self._log("send %d's leavesays"%id,self.LOG_DEBUG)
            r_code = self.__serverso.sendftmsg(id, ftmsg_bytes)
            if r_code >= 0:  # 这里删除数据库留言记录
                #self.__mysql.del_leavesays(id)
                self._log("del %d's leavesays"%id,self.LOG_DEBUG)

            return 0,None
        else:#这里为认证未通过，密码不符
            send_bytes = self.__ftmsg.pack_auth_rej(-2)
            self._log("anth rej:id =%d,reason is 'password not correct'." % id)
            return -1, send_bytes

    # 设计该消息，是为了避免链路断线恢复后需要用户用户名密码重新登录，由这条消息代替，用于指示链路，主要针对采用tcp的情况
    def __do_hello(self,so,id):#目前客户端本地不保存亲友字典，因而亲友字典在这里推送，留言也在这里推送
        # 首先判断该用户是否已经登录
        person = self.__persondict.get(id)
        if person is not None:
            if person.islogin():
                self.__serverso.soiswho(so,id)
                #推送亲友字典
                rfdict = self.__make_person_rfdict(id)
                self._log("make %d's rfdict:%s"%(id,str(rfdict)),self.LOG_DEBUG)
                ftmsg_bytes = self.__ftmsg.pack_rf_update(rfdict)
                self._log("rf update",self.LOG_DEBUG)
                self.__serverso.sendftmsg(id,ftmsg_bytes)

                #推送留言，留言推送要判断结果，根据成功与否来判断是否删除留言。
                leavesayslist = self.__make_person_leavesayslist(id)
                self._log("make %d's leavesayslist:%s" % (id, str(leavesayslist)), self.LOG_DEBUG)

                ftmsg_bytes = self.__ftmsg.pack_leavesays(leavesayslist)
                self._log("send %d's leavesays" % id, self.LOG_DEBUG)
                r_code = self.__serverso.sendftmsg(id,ftmsg_bytes)
                if r_code >0:#这里删除数据库留言记录
                    #self.__mysql.del_leavesays(id)
                    self._log("del %d's leavesays" % id, self.LOG_DEBUG)
                return 0, None
            else:
                return -1, None
        else:
            return -2, None



    def __do_dialog(self, sayswho, saysto, issaysinfamily, familyid, saystime, says):
        if issaysinfamily:
            #遍历family所有成员并发送
            family= self.__familydict.get(familyid)
            if family is not None:
                personids = family.getpersonids()
                for personid in personids:
                    person = self.__persondict.get(personid)
                    if person is not None:
                        if person.islogin():
                            #saystime 取服务器时间

                            saystime_in_server = int(round(time.time() * 1000))
                            print("saystime_in_server=",saystime_in_server)
                            ftmsg_bytes = self.__ftmsg.pack_dialog(sayswho,saysto,issaysinfamily,familyid,saystime_in_server,says)
                            self._log('send dialog message:sayswho=%d,saysto =%d,issaysinfamily=%s,familyid=%d,saystime=%d'%
                                      (sayswho,saysto,str(issaysinfamily),familyid,saystime_in_server),self.LOG_DEBUG)
                            r_code = self.__serverso.sendftmsg(personid,ftmsg_bytes)
                            if r_code < 0:#留言
                                self._log("add leavesays:id =%d,issaysinfamily = %s,familyid=%d,sayswho=%d,says=%s" %
                                          (person.getid(), str(issaysinfamily), familyid, sayswho, says))
                                self.__mysql.add_leavesays(person.getid(), issaysinfamily, familyid, sayswho, says)
        else:#not in family
            person= self.__persondict.get(saysto)
            if person is not None:
                if person.islogin():      #这里增加留言功能，增加留言就要记录用户在线状态，或者判断发送错误码
                    saystime_in_server = int(round(time.time() * 1000))
                    ftmsg_bytes = self.__ftmsg.pack_dialog(sayswho, saysto, issaysinfamily, familyid, saystime_in_server, says)
                    self._log('send dialog message:sayswho=%d,saysto =%d,issaysinfamily=%s,familyid=%d,saystime=%d' %
                              (sayswho, saysto, str(issaysinfamily), 0, saystime_in_server), self.LOG_DEBUG)
                    r_code = self.__serverso.sendftmsg(saysto, ftmsg_bytes)
                    if r_code <0:
                        self._log("add leavesays:id =%d,issaysinfamily = %s,familyid=%d,sayswho=%d,says=%s"%
                                  (person.getid(),str(issaysinfamily),0,sayswho,says))
                        self.__mysql.add_leavesays(person.getid(),issaysinfamily,familyid,sayswho,says)

    def serverdo_in_who(self,so, some_bytes):  # 初始时，会话传输不知道是哪个用户，需要调用这个接口将数据发过来
        if len(some_bytes) < 4:  # 错误消息，丢弃并返回-1
            return -1, None
        t, v_bytes = self.__ftmsg.unpack(some_bytes)
        # v_bytes合法性检查略,注意：不检查不防止攻击，所以必须要检查，待添加

        if t == self.__ftmsg.MSGC_CS_AUTH_REQ:
            id, password = self.__ftmsg.unpack_auth_req(v_bytes)
            self._log('serverdo_auth req：id = %d'%id, self.LOG_INFO)
            print("****",self.__do_auth_req)
            print(so,id,password)
            r_code, send_bytes = self.__do_auth_req(so,id, password)#？？？？TypeError: 'NoneType' object is not iterable
            if r_code == 0:
                return 0, send_bytes
            else:
                return -2, send_bytes
        elif t == self.__ftmsg.MSGC_CS_HELLO:
            id = self.__ftmsg.unpack_hello(v_bytes)
            self._log('serverdo_in_who: hello：id = %d'%id, self.LOG_INFO)
            r_code, send_bytes = self.__do_hello(so,id)
            if r_code == 0:
                return 0, send_bytes
            else:
                return -3, None
        else:
            self._log('serverdo_in_who: recv unkown data, drop.:%s' % str(some_bytes), self.LOG_INFO)
            return -4, None

    def serverdo_in(self, personid, ftmsg_bytes):
        # 解析somebytes
        t, v_bytes = self.__ftmsg.unpack(ftmsg_bytes)
        if t == self.__ftmsg.MSGD_DIALOG:
            sayswho,saysto, issaysinfamily, familyid, saystime, says = self.__ftmsg.unpack_dialog(v_bytes)
            self._log('serverdo_in:recv dialog message.sayswho=%d,saysto=%d,issaysinfamily=%s,familyid=%d,saystime=%d' % (
            sayswho,saysto, str(issaysinfamily), 0, saystime), self.LOG_DEBUG)
            self.__do_dialog(sayswho, saysto,issaysinfamily, familyid, saystime, says)
            pass
        else:
            self._log("serverdo_in:recv a unkown type ftmsg,personid=%d,msgtype= %d,dropped."%
                      (personid,t),self.LOG_INFO)
            pass
